#include "../../kartashov/chat/chat.h"
#include <stdlib.h>
#include <ncurses.h>
#include <unistd.h>
#include <string.h>
#include <iostream>
#include <vector>
#include <iterator>

#include <sys/socket.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <pthread.h>
#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <openssl/ssl.h>
#include <openssl/err.h>
#include <signal.h>

#define K_TAB 9
#define K_ENTER 10
#define K_ARROW 27

typedef struct sockaddr sockaddr_t;
typedef struct sockaddr_in sockaddr_in_t;

struct server_info_t {
  server_info_t()
    : proto_ver(0)
    , last_label(0)
  {}

  void set_proto_ver(char* ver) {
    if (proto_ver)
      free(proto_ver);
    proto_ver = ver;
  }
  const char* get_proto_ver() {
    return proto_ver;
  }
  void set_last_label(char* lbl) {
    if (last_label)
      free(last_label);
    last_label = lbl;
  }
  const char* get_last_label() {
    return last_label;
  }
  sockaddr_in_t saddr_in;
private:
  char* proto_ver;
  char* last_label;
};

struct message_t {
  message_t(char const* text, uint32_t text_len)
    : text(text, text_len)
  {}
  message_t(const std::string& text)
    : text(text)
  {}

  std::string text;
};

typedef std::vector<message_t> messages_t;

struct client_state_t {
  int sock;
  pthread_mutex_t sock_lock;
  int broadcast_sock;
  messages_t messages; 
  int active_win;
  std::string name;
  SSL_CTX *ssl_ctx;
  SSL_METHOD *meth;
  SSL *ssl_con;
};

#define SERVER_PORT 6789  
#define WIN_COUNT 3

struct server_info_t server;
struct client_state_t client = {};

static int read_all(int sock, char* buf, size_t len) {
  while (len) {
    struct pollfd pfd;
    pfd.fd = sock;
    pfd.events = POLL_IN | POLL_ERR | POLL_HUP;
    
    if (poll(&pfd, 1, 20000) == 0) {
      fprintf(stderr, "Data waiting timed out\n");
      return -1;
    }

    /*if (pfd.revents & (POLL_HUP | POLL_ERR)) {
      perror("Network error");
      return -1;
    }*/
    int res = SSL_read(client.ssl_con, buf, len);
    //int res = read(client.sock, buf, len);

    if (res < 0) {
      return res;
    }

    buf += res;
    len -= res;
  }
  return 0;
}

static int write_all(int sock, char *buf, int len) 
{   
  while (len) {
    struct pollfd pfd;
    pfd.fd = sock;
    pfd.events = POLL_OUT | POLL_ERR | POLL_HUP;
    if (poll(&pfd, 1, 20000) == 0) {
      fprintf(stderr, "Data waiting timed out\n");
      return -1;
    }

    /*if (pfd.revents & (POLL_HUP | POLL_ERR)) {
      perror("Network error");
      return -1;
    }*/
    int res = SSL_write(client.ssl_con, buf, len);
    //int res = write(client.sock, buf, len);
    if (res < 0) {
      return res;
    }

    buf += res;
    len -= res;
  }
  return 0;
}

int send_str(const char* str) {
  uint32_t str_len = (uint32_t)strlen(str);
  uint32_t msg_len = sizeof(str_len) + str_len;
  uint32_t buf_size = sizeof(uint32_t) + msg_len;
  
  char* buf = (char*)malloc(buf_size);
  memcpy(buf, (void*)&msg_len, sizeof(msg_len));
  memcpy(buf + sizeof(msg_len), (void*)&str_len, sizeof(str_len));
  memcpy(buf + sizeof(msg_len) + sizeof(str_len), str, msg_len);
  
  if (write_all(client.sock, buf, buf_size)) {
    perror("couldn't send message!");
    return -1;
  }
  free(buf);
  return 0;
}

int send_str_str(const char* str1, const char* str2) {
  uint32_t str_len1 = (uint32_t)strlen(str1);
  uint32_t str_len2 = (uint32_t)strlen(str2);
  uint32_t msg_len = str_len1 + sizeof(str_len1) + str_len2 + sizeof(str_len2);
  uint32_t buf_size = sizeof(msg_len) + msg_len;

  char* buf = (char*)malloc(buf_size);
  size_t buf_offset = 0;
  memcpy(buf + buf_offset, (void*)&msg_len, sizeof(msg_len));
  buf_offset += sizeof(msg_len);
  memcpy(buf + buf_offset, (void*)&str_len1, sizeof(str_len1));
  buf_offset += sizeof(str_len1);
  memcpy(buf + buf_offset, str1, str_len1);
  buf_offset += str_len1;
  memcpy(buf + buf_offset, (void*)&str_len2, sizeof(str_len2));
  buf_offset += sizeof(str_len2);
  memcpy(buf + buf_offset, str2, str_len2);
  
  if (write_all(client.sock, buf, buf_size)) {
    perror("couldn't send message!");
    return -1;
  }
  free(buf);
  return 0;
}

int send_str_str_str(const char* str1, const char* str2, const char* str3) {
  uint32_t str_len1 = (uint32_t)strlen(str1);
  uint32_t str_len2 = (uint32_t)strlen(str2);
  uint32_t str_len3 = (uint32_t)strlen(str3);
  uint32_t msg_len = str_len1 + sizeof(str_len1) + str_len2 + sizeof(str_len2) + str_len3 + sizeof(str_len3);
  uint32_t buf_size = sizeof(msg_len) + msg_len;

  char* buf = (char*)malloc(buf_size);
  size_t buf_offset = 0;
  memcpy(buf + buf_offset, (void*)&msg_len, sizeof(msg_len));
  buf_offset += sizeof(msg_len);
  memcpy(buf + buf_offset, (void*)&str_len1, sizeof(str_len1));
  buf_offset += sizeof(str_len1);
  memcpy(buf + buf_offset, str1, str_len1);
  buf_offset += str_len1;
  memcpy(buf + buf_offset, (void*)&str_len2, sizeof(str_len2));
  buf_offset += sizeof(str_len2);
  memcpy(buf + buf_offset, str2, str_len2);
  buf_offset += str_len2;
  memcpy(buf + buf_offset, (void*)&str_len3, sizeof(str_len3));
  buf_offset += sizeof(str_len3);
  memcpy(buf + buf_offset, str3, str_len3);
  buf_offset += str_len3;

  if (write_all(client.sock, buf, buf_size)) {
    perror("couldn't send message!");
    return -1;
  }
  free(buf);
  return 0;
}
/*
int send_num(uint32_t num) {

  uint32_t msg_len = sizeof(num);
  uint32_t buf_size = sizeof(msg_len) + msg_len;

  char* buf = (char*)malloc(buf_size);
  memcpy(buf, (void*)&msg_len, sizeof(msg_len));
  memcpy(buf + sizeof(msg_len), (void*)&num, msg_len);
  
  if (write_all(client.sock, buf, buf_size)) {
    perror("couldn't send message!");
    return -1;
  }
  free(buf);
  return 0;  
}
*/

uint32_t rcv_num(uint32_t *read) {
  uint32_t num = 0;
  read_all(client.sock, (char*)&num, sizeof(num));
  *read = sizeof(uint32_t);
  return num;
}

char* rcv_str(uint32_t *read) {
  uint32_t len = rcv_num(read);
  char* str = (char*)malloc(len + 1);
  read_all(client.sock, str, len);
  str[len] = '\0';
  *read += len;
  return str;
}

char** rcv_strs() {
  uint32_t read = 0;
  uint32_t all_len = rcv_num(&read);
  char* *strs = (char**)malloc(all_len);
  char* *it = strs;
  while (all_len) {
    read = 0;
    *it = rcv_str(&read);
    all_len -= read;
    ++it;
  }
  *it = 0;
  return strs;
}
/*
   char rcv_char(uint32_t *read) {
   char ch;
   read_all(client.sock, &ch, 1);
 *read += 1;
 return ch;
 }
 */
int send_hello() {
  pthread_mutex_lock(&client.sock_lock);
  const char* hello = "HELLO";
  uint32_t read = 0;
  send_str_str_str(hello, client.name.c_str(), "1");
  uint32_t resp_len = rcv_num(&read);
  //server.set_proto_ver(rcv_str(&read));
  //resp_len -= read;
  char *yn = rcv_str(&read);
  resp_len -= read;
  if (!strcmp(yn, "n")) {
    exit(-1);
  }
  free(yn);
  server.set_last_label(rcv_str(&read));
  resp_len -= read;
  free(rcv_str(&read));
  resp_len -= read;

  if (resp_len > 0) {
    printf("protocol options are not supported \n");
    //return -1;
  }
  printf("WARNING: Checking of protocol version is not supported\n");
  pthread_mutex_unlock(&client.sock_lock);
  return 0;
}

int send_get(messages_t& messages) {
  pthread_mutex_lock(&client.sock_lock);
  send_str_str("GET", server.get_last_label());
  uint32_t read = 0;
  rcv_num(&read); //resp len
  server.set_last_label(rcv_str(&read));
  char* smsg_len = rcv_str(&read);
  size_t msg_count = strtol(smsg_len, NULL, 10);
  free(smsg_len);
  while(msg_count) {
    char* msg = rcv_str(&read);
    messages.push_back(message_t(msg, read));
    --msg_count; 
  }
  pthread_mutex_unlock(&client.sock_lock);
  std::cerr << "GET sended" << std::endl;
  return 0;
}

char** send_list() {
  pthread_mutex_lock(&client.sock_lock);
  send_str("LIST");
  char* *list = rcv_strs();
  pthread_mutex_unlock(&client.sock_lock);
  return list;
}

int send_bye() {
  pthread_mutex_lock(&client.sock_lock);
  send_str("BYE");
  pthread_mutex_unlock(&client.sock_lock);
  return 0;
}

int send_msg(const char* msg) {
  pthread_mutex_lock(&client.sock_lock);
  send_str_str("SEND", msg);
  pthread_mutex_unlock(&client.sock_lock);
  return 0;
}

int setup_client_sock() {
  client.sock = socket(AF_INET, SOCK_STREAM, 0);
  if (client.sock == -1) {
    perror("Failed to create a TCP socket");
    return -1;
  }
  pthread_mutex_init(&client.sock_lock, NULL);
  return 0;
}

int setup_broadcast_sock() {
  client.broadcast_sock = socket(AF_INET, SOCK_DGRAM, 0);
  if (client.broadcast_sock == -1) {
    perror("Failed to create a UDP socket");
    return -1;
  }
  int opt_true = 1;
  setsockopt(client.broadcast_sock, SOL_SOCKET, SO_BROADCAST, &opt_true, sizeof(opt_true));

  sockaddr_in_t svr_host;
  memset(&svr_host, 0, sizeof(sockaddr_in_t));
  svr_host.sin_family = AF_INET;
  svr_host.sin_port = htons(SERVER_PORT);
  svr_host.sin_addr.s_addr = htonl(INADDR_BROADCAST);

  char buf[50] = {};
  errno = 0;
  sendto(client.broadcast_sock, buf, sizeof(buf), 0,  (sockaddr_t*)&svr_host, sizeof(sockaddr_in_t));
  if (errno) {
    perror("sendto ");
    return -1;
  }
  socklen_t len = sizeof(sockaddr_in_t);
  recvfrom(client.broadcast_sock, buf, sizeof(buf), 0, (sockaddr_t*)&server.saddr_in, &len);
  if (errno) {
    perror("recv");
    return -1;
  }
  return 0;
}

int connect_client() {
  if (connect(client.sock, (sockaddr_t*) &server.saddr_in, sizeof(sockaddr_in_t))) {
    perror("Couldn't connect to server");
    return -1;
  }
  return 0;
}

struct window_t {

  window_t(int width, int height, 
      int left, int top) 
    : top(top)
      , left(left)
      , width(width)
      , height(height)
      , win(win)
  {
    win = newwin(height, width, top, left);
    keypad(win, TRUE);
    bg_attr = attr_counter++;
    active_border_color = attr_counter++;
  }

  int top;
  int left;
  int width;
  int height;
  int cur_x;
  int cur_y;
  WINDOW* win;
  int bg_attr;
  int active_border_color;
  static int attr_counter;
  virtual void draw()=0;
  virtual void input(int key)=0;
  virtual ~window_t() {
    delwin(win);
  }
};
int window_t::attr_counter = 1;

typedef void (*id_changed_handler)(char* id);
typedef void (*bye_handler)();

struct menu_window_t: window_t {

  menu_window_t(int width, int height, int top, int left)
    : window_t(width, height, top, left)
  {
    init_pair(bg_attr, COLOR_WHITE, COLOR_MAGENTA);
    item_active = window_t::attr_counter++;
    item_inactive = window_t::attr_counter++;
    init_pair(item_active, COLOR_BLACK, COLOR_RED);  
    init_pair(item_inactive, COLOR_BLACK, COLOR_WHITE);  
    item_get = newwin(ITEM_HEIGHT, ITEM_WIDTH, top + height / (ITEM_COUNT*2), 1);
    item_list = newwin(ITEM_HEIGHT, ITEM_WIDTH, top + height / (ITEM_COUNT*2), 1 + ITEM_WIDTH);
    item_bye = newwin(ITEM_HEIGHT, ITEM_WIDTH, top + height / (ITEM_COUNT*2), 1 + ITEM_WIDTH*2);
    win_id = newwin(height, width, 0, 0);
    win_id_colors = window_t::attr_counter++;
    init_pair(win_id_colors, COLOR_WHITE, COLOR_RED);
    placeholder_colors = window_t::attr_counter++;
    init_pair(placeholder_colors, COLOR_BLACK, COLOR_WHITE);
    init_pair(active_border_color, COLOR_RED, COLOR_MAGENTA);
  }

  id_changed_handler id_changed;
  bye_handler bye_clicked;

  virtual void draw() {
    wclear(win);
    wattron(win, COLOR_PAIR(bg_attr));
    wbkgd(win, COLOR_PAIR(bg_attr));
    if (client.active_win == 0) {
      wattron(win, COLOR_PAIR(active_border_color));
      wborder(win, '*', '*', '*', '*', '*', '*', '*', '*');
      wattroff(win, COLOR_PAIR(active_border_color));
    } else {
      wborder(win, '|', '|', '-', '-', '+', '+', '+', '+');
    }
    wrefresh(win);
    wattroff(win, COLOR_PAIR(bg_attr));
    draw_items();
  }

  virtual void input(int key) {
    draw();
    if (client.active_win != 0)
      return;
    switch(key) {
      case KEY_RIGHT:
      case KEY_LEFT:
        active_item = (active_item + 1) % ITEM_COUNT;
        draw_items();
        break;
      case(K_ENTER):
        beep();
        //flash();
        if (active_item == 0) {
          char* id = get_id();
          draw();
          draw();
          id_changed(id);
        } else if (active_item == 1) {
          list();
          draw();
          draw();
        } else if (active_item == 2) {
          bye_clicked();
        }
        break;
    }
  }
  ~menu_window_t() {
    delwin(item_get);
    delwin(item_bye);
  }
  private:
  static const int ITEM_COUNT = 3;
  static const int ITEM_WIDTH = 10;
  static const int ITEM_HEIGHT = 5;
  WINDOW* item_bye;
  WINDOW* item_get;
  WINDOW* item_list;
  WINDOW* win_id;

  int active_item; 
  int item_active;
  int item_inactive;
  int win_id_colors;
  int placeholder_colors;

  void draw_items() {
    wclear(item_get);
    wclear(item_list);
    wclear(item_bye);
    wattron(item_get, COLOR_PAIR(item_inactive));
    wattron(item_list, COLOR_PAIR(item_inactive));
    wattron(item_bye, COLOR_PAIR(item_inactive));
    wbkgd(item_get, COLOR_PAIR(item_inactive));
    wbkgd(item_list, COLOR_PAIR(item_inactive));
    wbkgd(item_bye, COLOR_PAIR(item_inactive));
    if (active_item == 0) {
      wattron(item_get, COLOR_PAIR(item_active));
      wbkgd(item_get, COLOR_PAIR(item_active));
    } else if (active_item == 1) {
      wattron(item_list, COLOR_PAIR(item_active));
      wbkgd(item_list, COLOR_PAIR(item_active));
    } else if (active_item == 2) {
      wattron(item_bye, COLOR_PAIR(item_active));
      wbkgd(item_bye, COLOR_PAIR(item_active));
    }
    box(item_get, 0, 0);
    box(item_list, 0, 0);
    box(item_bye, 0, 0);
    mvwaddstr(item_get,  ITEM_HEIGHT/2, ITEM_WIDTH/2-1 , "GET");
    mvwaddstr(item_list, ITEM_HEIGHT/2, ITEM_WIDTH/2-1 , "LIST");
    mvwaddstr(item_bye,  ITEM_HEIGHT/2, ITEM_WIDTH/2-1 , "BYE");
    wmove(win, 0, 0);
    wrefresh(item_get);
    wrefresh(item_list);
    wrefresh(item_bye);
    wrefresh(stdscr);
  }

  char* get_id() {
    wclear(win_id);
    box(win_id, 0, 0);
    wattron(win_id, COLOR_PAIR(win_id_colors));
    wbkgd(win_id, COLOR_PAIR(win_id_colors));
    wmove(win_id, 4, 5);
    wprintw(win_id, "Please enter id from which to get messages:");
    wmove(win_id, 5, 5);
    wattroff(win_id, COLOR_PAIR(win_id_colors));
    wattron(win_id, COLOR_PAIR(placeholder_colors));
    wprintw(win_id, "                                           ");
    move(5, 5);
    wrefresh(win_id);
    char *id = (char*)malloc(20);
    int i = 0;
    char ch = 0;
    while(ch != '\n' && i != 18) {
      ch = getch();
      id[i] = ch;
      addch(ch);
      ++i;
    }
    id[i] = 0;
    wattroff(win_id, COLOR_PAIR(placeholder_colors));
    wclear(win_id);
    wrefresh(win_id);
    return id;
  }

  void list() {
    wclear(win_id);
    box(win_id, 0, 0);
    wattron(win_id, COLOR_PAIR(win_id_colors));
    wbkgd(win_id, COLOR_PAIR(win_id_colors));
    int strn = 1;
    char* *list = send_list();
    char* *it = list;
    for(; *it; ++it) {
      wmove(win_id, strn, 1);
      wprintw(win_id, *it);
      free(*it);
      ++strn;
    }
    wrefresh(win_id);
    wattroff(win_id, COLOR_PAIR(win_id_colors));
    free(list);
    getch();
    wmove(win_id, 0, 0);
    wclear(win_id);
    wrefresh(win_id);
  }
};

struct chat_window_t: window_t {

  chat_window_t(int width, int height, int top, int left)
    : window_t(width, height, top, left)
  {
    init_pair(bg_attr, COLOR_WHITE, COLOR_BLUE); 
    init_pair(active_border_color, COLOR_RED, COLOR_BLUE);
    cur_pos = 0;
    msg_visible = height - 2;
  }

  virtual void draw() {
    wclear(win);
    wbkgd(win, COLOR_PAIR(bg_attr));
    wattron(win, COLOR_PAIR(bg_attr));
    if (client.active_win == 1) {
      wattron(win, COLOR_PAIR(active_border_color));
      wborder(win, '*', '*', '*', '*', '*', '*', '*', '*');
      wattroff(win, COLOR_PAIR(active_border_color));
    } else {
      wborder(win, '|', '|', '-', '-', '+', '+', '+', '+');
    }
    show_messages();
    wrefresh(win);
    wattroff(win, COLOR_PAIR(bg_attr));
  }

  virtual void input(int key) {
    draw();
    if (client.active_win != 1)
      return;
    switch(key) {
      case(KEY_UP):
        cur_pos--;
        if (cur_pos < 0)
          cur_pos = 0;
        draw();
        break;
      case(KEY_DOWN):
        cur_pos++;
        if (cur_pos > (int)messages.size() - 1)
          cur_pos = messages.size() - 1;
        draw();
        break;
      case (K_ENTER):

        break;
      case (KEY_RIGHT):
        cur_pos = messages.size() - 1;
        draw();
        break;
      case (KEY_LEFT):
        cur_pos = 0;
        draw();
        break;
    }
  }

  void add_message(const message_t& msg) {
    messages.push_back(msg);
    draw();
    cur_pos = messages.size() - 1;
  }

  void clear_messages() {
    messages.clear();
    draw();
  }

  private:
  messages_t messages;
  int cur_pos;
  int msg_visible;
  void show_messages() {
    messages_t::iterator it = messages.begin();
    int to_show = (msg_visible > (int)messages.size() ? messages.size() : msg_visible);
    if (cur_pos - to_show > 0) {
      std::advance(it, cur_pos - to_show);
    } else {

    }
    int i = 0;
    for(; i != to_show; ++it) {
      wmove(win, i + 1, 0);
      waddstr(win, it->text.c_str());
      ++i;
    }
    wmove(win, 0, 0);
  } 
};

typedef void (*new_msg_handler)(char *msg);

struct message_window_t: window_t {

  message_window_t(int width, int height, int top, int left)
    : window_t(width, height, top, left)
  {
    init_pair(bg_attr, COLOR_WHITE, COLOR_BLACK);  
    init_pair(active_border_color, COLOR_RED, COLOR_BLACK);
  }

  virtual void draw() {
    wclear(win);
    wbkgd(win, COLOR_PAIR(bg_attr));
    wattron(win, COLOR_PAIR(bg_attr));
    if (client.active_win == 2) {
      wattron(win, COLOR_PAIR(active_border_color));
      wborder(win, '*', '*', '*', '*', '*', '*', '*', '*');
      wattroff(win, COLOR_PAIR(active_border_color));
    } else {
      wborder(win, '|', '|', '-', '-', '+', '+', '+', '+');
    }

    wrefresh(win);
    wattroff(win, COLOR_PAIR(bg_attr));
  }
  new_msg_handler new_msg;
  virtual void input(int key) {
    draw();
    if (key == K_TAB)
      return;
    if (client.active_win != 2)
      return;
    char *text = (char*)malloc(1000);
    text[0] = key;
    int i = 1;
    char ch = 0;
    int raw = 0;
    move(top + raw + 1, 2);
    addch(key);
    while(ch != '\n' && i != 998) {
      if ((i % width) == 0) {
        move(top + raw + 1, 2);
        ++raw;
      }
      ch = getch();
      /*
         if (ch == KEY_BACKSPACE) {
         --i;
         move(top + raw + 1, 2);
         continue;
         }*/
      text[i] = ch;
      addch(ch);
      ++i;
    }
    text[i] = 0;
    move(0, 0);
    draw();
    draw();
    new_msg(text);    
  }
  private:
  char* text;
};


chat_window_t* chat_window;
menu_window_t* menu_window;
message_window_t* message_window;

void get_to_window() {
  messages_t msgs;
  send_get(msgs); 
  messages_t::iterator it = msgs.begin();
  for(; it != msgs.end(); ++it) {
    chat_window->add_message(*it);
  }
}

void id_changed(char* id) {
  server.set_last_label(id);
  chat_window->clear_messages();
  get_to_window();
}

void bye_clicked() {
  send_bye();
  exit(0);
}

void new_msg(char* msg) {
  send_msg(msg);
  free(msg);
}

void setup_ncurses() {
  initscr();
  cbreak();
  noecho();
  keypad(stdscr, TRUE);
  start_color();
  bkgdset(COLOR_PAIR(COLOR_YELLOW));
  int maxx  = getmaxx(stdscr);
  int maxy = getmaxy(stdscr);
  client.active_win = 0;
  menu_window = new menu_window_t(maxx, 7, 0, 0);
  menu_window->id_changed = id_changed;
  menu_window->bye_clicked = bye_clicked;
  chat_window = new chat_window_t(maxx, maxy/2, 0, 7);
  message_window = new message_window_t(maxx, maxy/2 - 7, 0, 7 + maxy/2);
  message_window->new_msg = new_msg;
}

void cleanup_curses() {
  delete menu_window;
  delete chat_window;
  delete message_window;
  endwin();
}

void draw_gui() {
  //clear();
  menu_window->draw();
  chat_window->draw();
  message_window->draw();
  //refresh();
}

void sig_handler(int num) {
  std::cerr << "recieved signal " << num << std::endl;
}

pthread_t input_thread;
void* input_thread_work(void* arg) {
  
  signal(SIGPIPE, sig_handler);
  while(true) {
    int ch;
    ch = getch();
    if (ch == K_TAB) {
      client.active_win = (client.active_win + 1) % WIN_COUNT;
    }
    menu_window->input(ch);
    chat_window->input(ch);
    message_window->input(ch);
  }   
  return 0;
}

int client_loop() {
  if (send_hello())
    goto err;

  setup_ncurses();

  pthread_create(&input_thread, NULL, input_thread_work, 0);
  draw_gui();
  while(1) {
    draw_gui();
    sleep(3);
    get_to_window();
  }

  cleanup_curses();
err: return -1;
     return 0;
}

size_t setup_ssl() {
  
  SSL_library_init();
  SSL_load_error_strings();
  ERR_load_crypto_strings();
  BIO_new_fp(stderr, BIO_NOCLOSE);

  client.ssl_ctx = SSL_CTX_new(SSLv3_client_method());

  if (!client.ssl_ctx) {
    perror("Failed to create an OpenSSL context!");
    return -1;
  }
  
  if (!SSL_CTX_set_tmp_dh(client.ssl_ctx, get_dh())) {
    perror("Can't use dh");
  }

  if (!SSL_CTX_use_certificate_chain_file(client.ssl_ctx, "cert.csr")) {
    perror("Can't use cert.csr file");
  }

  if (!SSL_CTX_use_PrivateKey_file(client.ssl_ctx, "private.key", SSL_FILETYPE_PEM)) {
    perror("Can't use private.key file");
  }

  if (!SSL_CTX_load_verify_locations(client.ssl_ctx, "cacert.pem", 0)) {
    perror("Can't use CA");
  }

  client.ssl_con = SSL_new(client.ssl_ctx);
  if (!client.ssl_con) {
    perror("Failed to create an SSL connection!");
    return -1;
  }

  if (!SSL_set_fd(client.ssl_con, client.sock))
  {
    perror("Failed to setup SSL BIO!");
    return -1;
  }

  int ret = 0;
  
  if ((ret = SSL_connect(client.ssl_con)) != 1) {
    ret = SSL_get_error(client.ssl_con, ret);
    char buf[120];
    ERR_error_string(ret, buf);
    std::cerr << buf << std::endl;
    perror("Failed on ssl connect!");
    return -1;
  }

  return 0;
}

size_t cleanup_ssl() {
  if (client.ssl_ctx) {
    SSL_CTX_free(client.ssl_ctx);
  }
  return 0;
}

int main(int argc, char* argv[]) {

  std::cout << "Please enter you name" << std::endl;
  std::cin >> client.name;

  signal(SIGPIPE, sig_handler);

  //if (setup_broadcast_sock())
  //  goto clean1;
  server.saddr_in.sin_addr.s_addr = inet_addr(argv[1]);
  server.saddr_in.sin_family = AF_INET;
  server.saddr_in.sin_port = htons(CHAT_PORT);

  if (setup_client_sock())
    goto clean2;

  connect_client();

  std::cerr << "setting up ssl" << std::endl;

  if (setup_ssl())
    goto clean3;

  fcntl(client.sock, F_SETFL, O_NONBLOCK);
  
  client_loop();

clean3: cleanup_ssl();
clean2: close(client.sock); pthread_mutex_destroy(&client.sock_lock);
//clean1: close(client.broadcast_sock); 
        return 0;
}
